package com.mjsoftking.palettelib;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.ColorRes;

import com.mjsoftking.palettelib.bean.PaletteDrawConfig;
import com.mjsoftking.palettelib.bean.PalettePoint;
import com.mjsoftking.palettelib.list.PaletteDrawList;
import com.mjsoftking.palettelib.utils.ListUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 用途：画板视图，自定义继承控件后，使用此类即可简单集成画板
 * <p>
 * 注：布局和部分控件需要设置背景后才能绘制线段
 * 注：不要跨线程使用方法，避免出现异常
 * <p>
 * 作者：MJSoftKing
 */
public class BasePalette {

    //启用只有一次的标记
    boolean tempOnceMark;

    private View view;
    //画过的所有线
    private PaletteDrawList lines;
    private Paint paint;
    private Integer mColor;
    private float strokeWidth;
    private boolean removeLastNotLine;
    private boolean enableDraw;
    private boolean enableOnClick;
    private boolean enableOnce;
    private boolean enableEndToEnd;
    private boolean enableLine;
    private IDraw draw;

    private BasePalette(View view) {
        this.view = view;
        //如果View类本身实现了接口IDraw，那么无需再次调用setDraw()方法
        if (view instanceof IDraw) {
            setDraw((IDraw) view);
        }

        lines = new PaletteDrawList();

        setStrokeWidth(5);
        setPaintColor("#000000");
        setRemoveLastNotLine(true);
        setEnableDraw(true);
    }

    /**
     * 初始化对象
     */
    public static BasePalette create(View view) {
        return new BasePalette(view);
    }

    /**
     * 设置画笔颜色，HTML形式
     * <p>
     * 设置后下次落笔生效，先前已画线段不会改变
     * <p>
     * 先设置{@link BasePalette#setPaint(Paint)}后在使用此方法会清空paint的设置
     *
     * @param htmlColor 如：#FF000000
     */
    public void setPaintColor(String htmlColor) {
        mColor = Color.parseColor(htmlColor);
        //单独设置属性时，清除画笔引用
        this.paint = null;
    }

    /**
     * 设置画笔颜色，ColorRes资源
     * <p>
     * 设置后下次落笔生效，先前已画线段不会改变
     * <p>
     * 先设置{@link BasePalette#setPaint(Paint)}后在使用此方法会清空paint的设置
     */
    public void setPaintColor(@ColorRes int color) {
        mColor = this.view.getContext().getResources().getColor(color);
        //单独设置属性时，清除画笔引用
        this.paint = null;
    }

    /**
     * 设置画笔宽度
     * <p>
     * 设置后下次落笔生效，先前已画线段不会改变
     * <p>
     * 先设置{@link BasePalette#setPaint(Paint)}后在使用此方法会清空paint的设置
     *
     * @param width 单位像素
     */
    public void setStrokeWidth(float width) {
        strokeWidth = width;
        //单独设置属性时，清除画笔引用
        this.paint = null;
    }

    /**
     * 设置画笔宽度
     * <p>
     * 设置后下次落笔生效，先前已画线段不会改变
     * <p>
     * 先设置{@link BasePalette#setPaint(Paint)}后在使用此方法会清空paint的设置
     *
     * @param dbWidth 单位db
     */
    public void setStrokeDbWidth(int dbWidth) {
        strokeWidth = dipToPx(dbWidth);
        //单独设置属性时，清除画笔引用
        this.paint = null;
    }

    /**
     * 设置自定义画笔，优先级高于单属性控制
     * <p>
     * 设置后下次落笔生效，先前已画线段不会改变
     *
     * @param paint 自定义画笔对象
     */
    public void setPaint(Paint paint) {
        this.paint = paint;
    }

    /**
     * 设置是否启用绘制能力
     * <p>
     * 默认：启用
     */
    public void setEnableDraw(boolean enableDraw) {
        this.enableDraw = enableDraw;

        this.view.setClickable(enableDraw);
    }

    /**
     * 设置只画直线
     * <p>
     * 设置后清除所有线段，后续绘制获取点坐标时只会获得首尾坐标
     */
    public void setEnableLine(boolean enableLine) {
        this.enableLine = enableLine;
        clear();
    }

    /**
     * 获取所有线段
     * <p>
     * 每个线段下都有一组点的列表
     */
    public List<List<PalettePoint>> getLines() {
        return ListUtils.convert(lines, e -> {
            if (this.enableLine) {
                List<PalettePoint> pointList = new ArrayList<>();
                if (e.points.size() > 0) {
                    pointList.add(e.points.get(0));
                    pointList.add(e.points.get(e.points.size() - 1));
                }
                return pointList;
            } else {
                return e.points;
            }
        });
    }

    /**
     * 设置线段点坐标，使用形参内自带的画笔绘制，如未设置画笔，则使用Paint对象默认画笔
     * <p>
     * 坐标是基于所在view的像素点
     * <p>
     * 不能在非UI线程调用
     * <p>
     * 此方法不受{@link BasePalette#setEnableEndToEnd(boolean)}和
     * {@link BasePalette#setEnableOnce(boolean)}限制。
     */
    public void setLines(List<PaletteDrawConfig> l) {
        lines.clear();
        lines.addAll(l);

        this.view.invalidate();
    }

    /**
     * 设置线段点坐标，使用全局设置的默认画笔(所有画笔统一为最后一次设置的画笔)
     * <p>
     * 坐标是基于所在view的像素点
     * <p>
     * 不能在非UI线程调用
     * <p>
     * 此方法不受{@link BasePalette#setEnableEndToEnd(boolean)}和
     * {@link BasePalette#setEnableOnce(boolean)}限制。
     */
    public void setLinesUseDefaultPaint(List<PaletteDrawConfig> l) {
        lines.clear();
        for (PaletteDrawConfig config : l) {
            addPaint(config);
            lines.add(config);
        }

        this.view.invalidate();
    }

    /**
     * 当触摸抬起时，所画线段只是起点坐标或者所有点坐标均相同时，移除此次所画线段，因为他不是线段只是点
     * <p>
     * 默认启用此策略
     */
    public void setRemoveLastNotLine(boolean removeLastNotLine) {
        this.removeLastNotLine = removeLastNotLine;
    }

    /**
     * 是否响应 onClick 事件
     * <p>
     * 默认：关闭
     * 启用时，触摸抬起时触摸点在控件上时会响应点击事件，反之不响应
     * 关闭时，不响应点击事件
     */
    public void setEnableOnClick(boolean enableOnClick) {
        this.enableOnClick = enableOnClick;
    }

    /**
     * 设置是否只能绘制一次，第二次绘制时触摸事件将会向下传递
     * 注：仅在绘制缓存为0时可以设置，否则设置均会失败
     * <p>
     * 默认：关闭，可绘制多次
     *
     * @return true: 设置成功，false: 设置失败
     */
    public boolean setEnableOnce(boolean enableOnce) {
        if (lines.size() == 0) {
            this.enableOnce = enableOnce;
            return true;
        }
        return false;
    }

    /**
     * 设置是否启用首尾相连
     * <p>
     * 注：仅在至少已有3个坐标点时，首尾相连才可成立
     * <p>
     * 默认：关闭，启用时，最后一次绘制的点与首点形成线段，即将起点加入到列表末尾
     */
    public void setEnableEndToEnd(boolean enableEndToEnd) {
        this.enableEndToEnd = enableEndToEnd;
    }

    /**
     * 设置自定义的绘制方法，自行绘制所有点
     * <p>
     * 默认：以线段的方式绘制
     */
    public void setDraw(IDraw draw) {
        this.draw = draw;
    }

    /**
     * 撤销上一次绘制
     */
    public void revocation() {
        lines.revocation();
        this.view.invalidate();
    }

    /**
     * 清空绘制
     */
    public void clear() {
        lines.clear();
        this.view.invalidate();
    }

    /**
     * 获取视图的截图
     */
    public Bitmap screenShot() {
        // View是你需要截图的View
        boolean enabled = this.view.isDrawingCacheEnabled();
        if (!enabled) {
            this.view.setDrawingCacheEnabled(true);
        }

        this.view.buildDrawingCache();
        Bitmap bitmap = this.view.getDrawingCache();

        Bitmap b = Bitmap.createBitmap(bitmap);
        this.view.destroyDrawingCache();

        if (!enabled) {
            this.view.setDrawingCacheEnabled(false);
        }
        return b;
    }

    /**
     * 将视图的截图保存到指定的文件
     *
     * @param fileName 全路径携带文件名，绝对路径
     * @param format   格式，参考{@link Bitmap.CompressFormat}
     * @param quality  压缩质量，参考{@link Bitmap.CompressFormat}
     */
    public void saveBitmap(String fileName, Bitmap.CompressFormat format, int quality) throws IOException {
        File saveFile = new File(fileName);
        //文件名最后一位是分隔符时，算作文件夹处理
        if (fileName.lastIndexOf(File.separator) == (fileName.length() - 1)) {
            if (!saveFile.exists()) {
                //noinspection ResultOfMethodCallIgnored
                saveFile.mkdirs();
            }
            saveFile = new File(fileName + System.currentTimeMillis() + ".png");
        } else {
            File p = saveFile.getParentFile();
            if (null != p && !p.exists()) {
                //noinspection ResultOfMethodCallIgnored
                p.mkdirs();
            }
        }

        FileOutputStream saveImgOut = new FileOutputStream(saveFile);
        Bitmap bm = screenShot();
        //compress
        bm.compress(format, quality, saveImgOut);
        //存储完成后需要清除相关的进程
        saveImgOut.flush();
        saveImgOut.close();
    }

    /**
     * 将视图的截图保存到指定的文件
     * <p>
     * 默认保存到：存储-Android-data-包名-files-images文件夹下
     * 文件格式 png
     * png格式质量字段被忽略
     */
    public String saveBitmap() throws IOException {
        String fileName = this.view.getContext().getExternalFilesDir("").getAbsolutePath()
                + File.separator + "images" + File.separator + System.currentTimeMillis() + ".png";
        saveBitmap(fileName,
                Bitmap.CompressFormat.PNG,
                0);
        return fileName;
    }

    /**
     * 在 onTouchEvent 下执行，返回false时执行父类方法，反之返回true
     */
    public boolean onTouchEvent(MotionEvent event) {
        //未启用绘制能力时，向下传递事件
        if (!enableDraw) {
            return false;
        }

        //获取坐标
        float clickX = event.getX();
        float clickY = event.getY();

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (!enableOnce || lines.size() == 0) {
                //添加线段点存储对象，必须先添加
                lines.add(new PaletteDrawConfig());
                //添加画笔
                addPaint(lines.getLastLine());
                //添加起点坐标
                addPoint(clickX, clickY);

                tempOnceMark = true;
            } else {
                tempOnceMark = false;
            }
            return tempOnceMark;
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (tempOnceMark) {
                //在移动时添加所有经过的点
                addPoint(clickX, clickY);
                //刷新屏幕
                this.view.invalidate();
            }
            return tempOnceMark;
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            if (tempOnceMark) {
                if (this.removeLastNotLine) {
                    if (!lines.removeLastNotLine() && enableEndToEnd) {
                        PaletteDrawConfig config = lines.getLastLine();
                        //存在至少3个点时，首尾相连成立
                        if (config.points.size() > 2) {
                            config.points.add(config.points.get(0).clone());
                            this.view.invalidate();
                        }
                    }
                } else if (enableEndToEnd) {
                    PaletteDrawConfig config = lines.getLastLine();
                    //存在至少3个点时，首尾相连成立
                    if (config.points.size() > 2) {
                        config.points.add(config.points.get(0).clone());
                        this.view.invalidate();
                    }
                }

                if (enableOnClick &&
                        clickX <= this.view.getWidth() && clickX >= 0 &&
                        clickY <= this.view.getHeight() && clickY >= 0) {
                    this.view.performClick();
                }
            }
            return tempOnceMark;
        } else {
            return false;
        }
    }

    /**
     * 绘制，在 onDraw 下执行
     *
     * @param canvas 画布
     */
    public void onDraw(Canvas canvas) {
        if (null != draw) {
            draw.onDraw(canvas, lines);
        } else {
            //画出所有的线
            for (int i = 0; i < lines.size(); i++) {
                drawLine(canvas, lines.get(i));
            }
        }
    }

    /**
     * 以线段的方式绘制，默认绘制方法
     */
    public void drawLine(Canvas canvas, PaletteDrawConfig line) {
        if (this.enableLine) {
            if (line.points.size() > 0) {
                float x = line.points.get(0).x;
                float y = line.points.get(0).y;
                float nextX = line.points.get(line.points.size() - 1).x;
                float nextY = line.points.get(line.points.size() - 1).y;
                canvas.drawLine(x, y, nextX, nextY, line.paint);
            }
        } else {
            for (int i = 0; i < line.points.size() - 1; i++) {
                float x = line.points.get(i).x;
                float y = line.points.get(i).y;
                float nextX = line.points.get(i + 1).x;
                float nextY = line.points.get(i + 1).y;
                canvas.drawLine(x, y, nextX, nextY, line.paint);
            }
        }
    }

    /**
     * 添加画笔
     */
    private void addPaint(PaletteDrawConfig line) {
        //如果没使用自定义画笔，则按照特定属性修改画笔
        if (null == paint) {
            line.paint = new Paint();

            if (null != mColor) {
                line.paint.setColor(mColor);
            }
            if (strokeWidth > 0) {
                line.paint.setStrokeWidth(strokeWidth);
            }
        }
        //使用自定义画笔
        else {
            line.paint.set(paint);
        }
    }

    /**
     * 添加坐标
     */
    private void addPoint(float clickX, float clickY) {
        PalettePoint point = new PalettePoint();
        point.x = clickX;
        point.y = clickY;

        //在移动时添加所有经过的点
        PaletteDrawConfig config = lines.getLastLine();
        if (!config.points.contains(point)) {
            config.points.add(point);
        }
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public float dipToPx(float dpValue) {
        final float scale = this.view.getContext().getResources().getDisplayMetrics().density;
        return dpValue * scale + 0.5f;
    }

    /**
     * 自定义绘制的实现接口
     */
    public interface IDraw {
        void onDraw(Canvas canvas, List<PaletteDrawConfig> lines);
    }

}
